package br.com.montezuma.empresarial.backend.core;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Projections;

import br.com.montezuma.empresarial.utils.Util;

/**
 * <pre>
 * @author : David Montezuma          
 * Purpose      :   Generic DAO, providing basic CRUD operations implementation.
 * Amendment History                                                                
 * Date - Name - Description                              
 * 20/05/2012 - David Montezuma - initial version.
 * 
 * Copyright 2012 Montezuma Software
 * </pre>
 */
public class DAO<T extends Object, ID extends Serializable> implements IDAO<T, ID> {

	// ~ Instance fields
	// --------------------------------------------------------

	private final Class<T> persistentClass;
	private SessionFactory entityManager;
	private Session session;
	private Transaction transaction;

	// ~ Constructors
	// -----------------------------------------------------------

	@SuppressWarnings("unchecked")
	public DAO() throws ClassNotFoundException {
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		Class.forName("com.mysql.jdbc.Driver");
		createEntityManager();
	}

	public DAO(final Class<T> persistentClass) throws ClassNotFoundException {
		super();
		Class.forName("com.mysql.jdbc.Driver");
		this.persistentClass = persistentClass;
	}

	// ~ Methods
	// ----------------------------------------------------------------

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#countAll()
	 */
	public int countAll() {
		return countByCriteria();
	}
	
	public void open(){
		session =  getEntityManager().openSession();
	}
	
	public void beginTransaction(){
		transaction = session.beginTransaction();
	}
	
	public void commitTransaction(){
		transaction.commit();
	}
	
	public void rollbackTransaction(){
		transaction.rollback();
	}

	public void close(){
		session.close();
	}
	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#countByExample(java.lang.Object)
	 */
	public int countByExample(final T exampleInstance) {
		Criteria crit = session.createCriteria(getEntityClass());
		crit.setProjection(Projections.rowCount());
		crit.add(Example.create(exampleInstance));
		int count = (Integer) crit.list().get(0);
		return count;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#findAll()
	 */
	public List<T> findAll() {
		return findByCriteria();
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#findByExample(java.lang.Object)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByExample(final T exampleInstance) {
		Criteria crit = session.createCriteria(getEntityClass());
		final List<T> result = crit.list();
		return result;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#findById(java.io.Serializable)
	 */
	public T findById(final ID id) {
		session.load(persistentClass, id);
		final T result = (T) session.load(persistentClass, id);
		return result;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository
	 *      #findByNamedQuery(java.lang.String, java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(final String name, Object... params) {
		 Query query = session.getNamedQuery(
				name);

		for (int i = 0; i < params.length; i++) {
			query.setParameter(i + 1, params[i]);
		}
		

		final List<T> result = Util.iteratorToList(query.iterate());
		return result;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository
	 *      #findByNamedQueryAndNamedParams(java.lang.String, java.util.Map)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByNamedQueryAndNamedParams(final String name,
			final Map<String, ? extends Object> params) {
		Query query = session.getNamedQuery(
				name);

		for (final Map.Entry<String, ? extends Object> param : params
				.entrySet()) {
			query.setParameter(param.getKey(), param.getValue());
		}

		final List<T> result =  Util.iteratorToList(query.iterate());
		return result;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#getEntityClass()
	 */
	public Class<T> getEntityClass() {
		return persistentClass;
	}

	/**
	 * set the JPA entity manager to use.
	 * 
	 * @param entityManager
	 */
	@PersistenceContext
	public void seSessionFactory(final SessionFactory entityManager) {
		this.entityManager = entityManager;
	}

	public SessionFactory getEntityManager() {
		return entityManager;
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected List<T> findByCriteria(final Criterion... criterion) {
		return findByCriteria(-1, -1, criterion);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	protected List<T> findByCriteria(final int firstResult,
			final int maxResults, final Criterion... criterion) {
		Criteria crit = session.createCriteria(getEntityClass());

		for (final Criterion c : criterion) {
			crit.add(c);
		}

		if (firstResult > 0) {
			crit.setFirstResult(firstResult);
		}

		if (maxResults > 0) {
			crit.setMaxResults(maxResults);
		}

		final List<T> result = crit.list();
		return result;
	}

	protected int countByCriteria(Criterion... criterion) {
		Criteria crit = session.createCriteria(getEntityClass());
		crit.setProjection(Projections.rowCount());

		for (final Criterion c : criterion) {
			crit.add(c);
		}

		int i =  (Integer) crit.list().get(0);
		return i;
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository#delete(java.lang.Object)
	 */
	public void delete(T entity) {
		session.delete(entity);
	}

	/**
	 * @see be.bzbit.framework.domain.repository.GenericRepository
	 *      #save(java.lang.Object)
	 */
	public void save(T entity) {
		session.merge(entity);

	}


	public SessionFactory createEntityManager() {
		SessionFactory em = SessionFactorySingleton.getInstance().getSessionFactory();
		seSessionFactory(em);
		return em;
	}

}